• 在gawk编程语言中,你可以做下面的事情:
    • 定义变量来保存数据
    • 使用算术和字符串操作符来处理数据
    • 使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑
    • 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告
  • gawk程序的基本格式如下:

    1
    gawk options program file

    下面是gawk程序的可用选项:

    选项 描述
    -F fs 指定行中划分数据字段的字段分隔符
    -f file 从指定的文件中读取程序
    -v var=value 定义gawk程序中的一个变量及其默认值
    gawk的强大之处在于程序脚本,可以写脚本来读取文本行的数据,然后处理并显示数据,创建任何类型的输出报告

  • gawk程序脚本使用一对花括号来定义,你必须将脚本命令放到两个花括号中,由于gawk命令行假定脚本是单个文本字符串,你必须将脚本放到单引号中:

    1
    $ gawk '{print "Hello World"}'

    当你在shell中执行这个命令时,shell中不会有任何输出,这是因为没有在命令行中指定文件名,所以gawk程序会从STDIN接收数据,在运行这个程序的时候,它会一直等待从STDIN输入的文本。 gawk程序会对每行文本执行脚本程序。

    Ctrl+D组合键会在bash中产生一个EOF字符,这个组合键能够终止该gawk程序并返回到命令行界面提示符下

  • gawk的主要特性之一是其处理文本文件中的数据的能力,它会自动给一行中的每个数据元素分配一个变量,默认情况下,gawk会将如下变量分配给它在文本行中发现的数据字段:

    • $0代表整个文本行
    • $1代表文本行中的第一个数据字段
    • $2代表文本行中的第二个数据字段
    • $n代表文本行中的第n个数据字段

    在文本行中,每个数据字段都是通过字段分隔符来划分的,gawk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段,gawk中默认的字段分隔符是任意的空白字符(例如空格或制表符),下面的程序会读取文本文件,只显示第一个数据字段的值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ nano data.txt
    One line of test text.
    Two lines of test text.
    Three lines of test text.
    $ gawk '{print $1}' data.txt
    One
    Two
    Three
    $

    如果你要读取采用了其他字段分隔符的文件,可以用-F选项指定:

    1
    $ gawk -F: '{print $1}' /etc/passwd
  • 要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可;

    1
    2
    $ echo "My name is Rich" | gawk '{$4="gtp"; print $0}'
    My name is gtp
  • 从文件中读取程序:gawk编辑器允许将程序存储在文件中,然后在命令行中引用:

    1
    2
    3
    $ nano script.gawk
    {print $1 "'s home directory is " $6}
    $ gawk -F: -f script.gawk /etc/passwd
  • BEGIN关键字可以在处理数据前运行脚本,它会强制gawk在读取数据前指定BEGIN关键字后指定的程序脚本:

    1
    2
    3
    $ gawk 'BEGIN{print "Hello World!"}'
    Hello World!
    $
  • END关键字允许你指定一个程序脚本,gawk会在读完数据后执行它:

    1
    2
    3
    $ gawk 'BEGIN {print "The data3 File Contents:"}'
    > {print $0}
    > END {print "End of File"}' data.txt
  • 可以将所有内容放在一起组成一个脚本文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ nano script.gawk
    BEGIN {
    print "The latest list of users and shells"
    print "UserID \t Shell"
    print "------ \t ------"
    FS=":"
    }
    {
    print $1 " \t " $7
    }
    END {
    print "This concludes the listing"
    }
  • 内建变量FS可以更改字段分隔符,还有其他一些内建变量:

    • FIELDWIDTHS 由空格分隔的一列数字,定义了每个数据字符的确切宽度
      • FS 输入字段分隔符
      • RS 输入记录分隔符
    • OFS 输出字段分隔符
    • ORS 输出记录分隔符

    默认情况下,gawk将OFS设为一个空格,所以如果你用命令:

    print $1, $2, $3会看到如下输出:

    field1 field2 field3

    print命令会自动将OFS变量的值放置在输出中的每个字符间,通过设置OFS变量,可以在输出中使用任意字符串来分隔字段:

    1
    2
    3
    4
    $ gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
    data11-data12-data13
    data21-data22-data23
    data31-data32-data33

    变量RS和ORS定义了gawk程序如何处理数据流中的记录,默认情况下,gawk将RS和ORS设为换行符,默认的RS表明,输入数据流中的每行新文本就是一条新记录。

    有时你会在数据流中碰到占据多行的记录,例如:

    1
    2
    3
    4
    Riley Mullen
    123 Main Street
    Chicago, IL 60601
    (312)555-1234

    如果使用默认的FS和RS变量值来读取这段数据,gawk就会把每行作为一条单独的记录来读取,并将记录中的空格当作字段分隔符,为了解决这个问题,只需要把FS变量值设置为换行符,这就表明数据流中的每行都是一个单独的字段,每行上的所有数据都属于同一个字段,现在还有一个问题就是无从判断一个新的数据行从何开始,对于这个问题,可以将RS变量设置为空字符串,然后在数据记录之间留一个空白行,gawk会把每个空白行当作一个记录分隔符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $ cat data2
    Riley Mullen
    123 Main Street
    Chicago, IL 60601
    (312)555-1234
    Frank Williams
    456 Oak Street
    Indianapolis, IN 46201
    (317)555-9876
    Haley Snell
    4231 Elm Street
    Detroit, MI 48201
    (313)555-4938
    $ gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
    Riley Mullen (312)555-1234
    Frank Williams (317)555-9876
    Haley Snell (313)555-4938
    $
  • ARGC和ARGV变量允许从shell中获得命令行参数的总数以及它们的值,但是gawk并不会将程序脚本当成命令行参数的一部分:

    1
    2
    $ gawk 'BEGIN {print ARGC, ARGV[1]}' data1
    2 data1

    ARGC变量表示命令行上有两个参数,这包括gawk命令和data1参数,需要注意的是程序脚本并不算参数,ARGV数组从索引0开始,代表的是命令,第一个数组值是gawk命令后的第一个命令行参数

  • NF变量可以让你在不知道具体位置的情况下指定记录中的最后一个数据字段
  • 可以在gawk命令行上给程序中的变量赋值,这允许你在正常的代码之外赋值,及时改变变量的值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ cat script1
    BEGIN{FS=","}
    {print $n}
    $ gawk -f script1 n=2 data1
    data12
    data22
    data32
    $ gawk -f script1 n=3 data1
    data13
    data23
    data33
    $

    使用命令行参数来定义变量值会有一个问题,在你设置了变量后,这个值在代码的BEGIN部分不可用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ cat script2
    BEGIN{print "The starting value is",n; FS=","}
    {print $n}
    $ gawk -f script2 n=3 data1
    The starting value is
    data13
    data23
    data33
    $

    可以用-v命令行参数来解决这个问题,它允许你在BEGIN代码之前设定变量,在命令行上,-v命令行参数必须放在脚本代码之前:

    1
    gawk -v n=3 -f script data1
  • gawk编程语言使用关联数组提供数组功能,关联数组和数字数组的不同之处在于它的索引值可以是任意字符串,你不需要使用连续的数字来标识数组中的数据元素,相反,关联数组使用各种字符串来引用值,每个索引字符串都必须能够唯一地标识出赋给它的数据元素

  • 数组变量赋值的格式为:var[index] = element,其中var是变量名,index是关联数组的索引值,element是数组元素值,例如:

    1
    2
    3
    4
    5
    6
    $ gawk 'BEGIN{
    > capital["Illinois"] = "Springfield"
    > print capital["Illinois"]
    > }'
    Springfield
    $
  • 如果要在gawk中遍历一个关联数组,可以用for语句的一种特殊形式:

    1
    2
    3
    for (var in array) {
    statements;
    }

    这个for语句会在每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一遍statements:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ gawk 'BEGIN{
    > var["a"] = 1
    > var["g"] = 2
    > var["m"] = 3
    > var["u"] = 4
    > for (test in var)
    > {
    > print "Index:",test," - Value:",var[test]
    > }
    > }'
    Index: u - Value: 4
    Index: m - Value: 3
    Index: a - Value: 1
    Index: g - Value: 2
    $

    需要注意的是,索引值不会按任何特定顺序返回,但他们都能够执行对应的数据元素值。

    如果需要从关联数组中删除数组索引需要使用下面的命令:

    1
    delete array[index]
  • 在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号之前:

    1
    2
    3
    $ gawk 'BEGIN{FS=","} /11/{print $1}' data1
    data11
    $

    正则表达式/11/匹配了数据字段中含有字符串11的记录,gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分隔符:

    1
    2
    3
    4
    $ gawk 'BEGIN{FS=","} /,d/{print $1}' data1
    data11
    data21
    data31
  • 匹配操作符允许将正则表达式限定在记录中的特定数据字段,匹配操作符是波浪先(~),可以指定匹配操作符、数据字段变量、以及要匹配的正则表达式:

    1
    $1 ~ /^data/

    $1变量代表记录中的第一个数据字段,这个表达式会过滤出第一个字段以文本data开头的所有记录:

    1
    2
    3
    $ gawk 'BEGIN {FS=","} 2 ~ /^data2/{print $0}' data1
    data21,data22,data23
    $

    匹配操作符会用正则表达式/^data2/来比较第二个数据字段,该正则表达式指明字符串要以文本data2开头。

    1
    2
    3
    $ gawk -F: '$1 ~ /rich/{print $1,$NF}' /etc/passwd
    rich /bin/bash
    $

    这个例子会在第一个数据字段中查找文本rich,如果记录中找到了这个模式,它会打印该记录的第一个和最后一个数据字段值

  • 除了正则表达式,你也可以在匹配模式中用数学表达式,这个功能在匹配数据字段中的数字值时非常方便,举个例子,如果你想显示所有属于root用户组(组ID为0)的系统用户:

    1
    2
    3
    $ gawk -F: '4 == 0{print $1}' /etc/passwd
    root
    $

    这段脚本会查看第四个数据字段含有值为0的记录。

    也可以对文本数据使用表达式,但必须小心,跟正则表达式不同,表达式必须完全匹配,数据必须跟模式严格匹配。

  • if语句的格式:

    1
    2
    if (condition)
    statement1

    当然也可以放在一行上:

    1
    if (condition) statement1
  • while语句的格式:

    1
    2
    3
    while (condition) {
    statements
    }

    举个例子如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ gawk '{
    > total = 0
    > i = 1
    > while (i < 4) {
    > total += $i
    > i++
    > }
    > avg = total / 3
    > print "Average: ", avg
    > }' data
    Average: 128.333
    Average: 137.667
    Average: 176.667
    $

    这里需要注意的是gawk脚本程序会对数据中的每一行都执行一遍

  • for语句的格式:

    1
    for ( variable assignment; condition; iteration process )
  • gawk可以格式化打印输出,此时可以使用printf指令:

    1
    printf "format string", var1, var2

    格式化指定符采用如下格式:

    1
    %[modifier] control-letter

    其中control-letter是一个单子符代码,用来指明显示什么类型的数据,而modifier定义了可选的格式化特性。

  • gawk中定义自己的函数,必须用function关键字:

    1
    2
    3
    function name([variables]) {
    statements
    }

    可以在调用gawk程序中传给这个函数一个或多个变量,当然,函数还能用return语句返回值:return value

  • 在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ gawk '
    > function myprint()
    > {
    > printf "%-16s - %s\n", 1, 4
    > }
    > BEGIN {FS="\n"; RS=""}
    > {
    > myprint()
    > }' data1
    Riley Mullen - (312)555-1234
    Frank Williams - (317)555-9876
    Haley Snell - (313)555-4938
  • 创建函数库:

    首先,创建一个存储所有gawk函数的文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ cat funclib
    function myprint() {
    printf "%-16s - %s\n", $1, $4
    }
    function myrand(limit) {
    return int(limit * rand())
    }
    function printthird() {
    print $3
    }
    $

    funclib文件含有三个函数定义,需要使用-f命令行参数来使用他们,很遗憾,不能将-f命令行参数和内联gawk脚本放在一起使用,不过可以在同一个命令行中使用多个-f参数,因此要使用库,只要创建一个含有你的gawk程序的文件,然后在命令行上同时指定库文件和程序文件就行了:

    1
    2
    3
    4
    5
    6
    $ cat script4
    BEGIN { FS="\n"; RS=""}
    {
    myprint()
    }
    $ gawk -f funclib -f script4 data2
  • 处理数据文件时,关键是要先把相关的记录放在一起,然后对相关数据进行必要的计算,举个例子如下:

    我们手边有一个数据文件,其中包含了两个队伍(每对两名选手)的保龄球比赛得分情况:

    1
    2
    3
    4
    5
    $ cat scores.txt
    Rich Blum,team1,100,115,95
    Barbara Blum,team1,110,115,100
    Christine Bresnahan,team2,120,115,118
    Tim Bresnahan,team2,125,112,116

    每位选手都有三场比赛的成绩,这些成绩保存在数据文件中,每位选手由位于第二列的队名来标识,下面的脚本对每对的成绩进行了排序,并计算了总分和平均分。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ cat bowling.sh
    #!/bin/bash
    for team in $(gawk -F, {print $2} scores.txt | unique)
    do
    gawk -v team=$team 'BEGIN{FS=","; total=0}'
    {
    if ($2 == team)
    {
    total += $3 + $4 + $5
    }
    }
    END {
    avg = total / 6;
    print "Total for", team, "is", total, ", the average is", avg
    }' scores.txt
    done
    $

    for 循环中的第一条语句过滤出数据文件中的队名,然后使用uniq命令返回不重复的队名,for循环再对每个队进行迭代

    1
    2
    3
    4
    $ ./bowling.sh
    Total for team1 is 635, the average is 105.833
    Total for team2 is 706, the average is 117.667
    $